package com.example.demo.aspect;

import com.example.demo.anno.MyLog;
import com.example.demo.model.SysLog;
import com.example.demo.service.SysLogService;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Aspect
@Component  //把普通pojo实例化到spring容器中，相当于配置文件中的<bean id="" class=""/>
public class SysLogAspect {
    @Autowired
    private SysLogService sysLogService;
    /** 换行符 */
    private static final String LINE_SEPARATOR = System.lineSeparator();

    /**
     * 这里我们使用注解的形式
     * 当然，我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
     * @annotation 匹配那些有指定注解的连接点
     * within 指定匹配必须包括某个注解的的类里的所有连接点
     * execution是一种使用频率比较高比较主要的一种切点指示符，用来匹配方法签名，方法签名使用全限定名，
     * 包括访问修饰符（public/private/protected）、返回类型，包名、类名、方法名、参数，其中返回类型，包名，类名，方法，参数是必须的
     * 切点表达式:   execution(...)
     * execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
     * modifiers-pattern：方法的可见性，如public，protected；
     * ret-type-pattern：方法的返回值类型，如int，void等；
     * declaring-type-pattern：方法所在类的全路径名，如com.example.demo.controller；
     * name-pattern：方法名类型，test()；
     * param-pattern：方法的参数类型，如java.lang.String；
     * throws-pattern：方法抛出的异常类型，如java.lang.Exception；
     *  *通配符，该通配符主要用于匹配单个单词
     *  ..通配符，该通配符表示0个或多个项
     */
    @Pointcut("@annotation(com.example.demo.anno.MyLog)")
    //@Pointcut("execution (* com.example.demo.controller..*(..))")
    public void logPointCut() {}

    /**
     * 环绕通知 @Around  ， 当然也可以使用 @Before (前置通知)  @After (后置通知)
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
        try {
            saveLog(point, time);
        } catch (Exception e) {
        }
        return result;
    }

    @Before("logPointCut()")
    public void doBefore(){
        //log.info("方法执行前");
        log.info("========================================== Start ==========================================");
    }

    @After("logPointCut()")
    public void doAfter(){
        //log.info("方法执行后");
        log.info("=========================================== End ===========================================" + LINE_SEPARATOR);
    }

    /**
     * 保存日志
     * @param joinPoint
     * @param time
     */
    private void saveLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        SysLog sysLog = new SysLog();
        sysLog.setExeuTime(time);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        sysLog.setCreateDate(dateFormat.format(new Date()));
        MyLog myLog = method.getAnnotation(MyLog.class);
        if(myLog != null){
            //注解上的描述
            sysLog.setRemark(myLog.value());
        }
        String url=request.getRequestURL().toString();
        String methods=request.getMethod();
        String ip=request.getRemoteAddr();
        sysLog.setUrl(url);
        sysLog.setIp(ip);
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setClassName(className);
        sysLog.setMethodName(methodName);
        //请求的参数
        Object[] args = joinPoint.getArgs();
        try{
            List<String> list = new ArrayList<String>();
            for (Object o : args) {
                list.add(new Gson().toJson(o));
            }
            sysLog.setParams(list.toString());
        }catch (Exception e){ }
        // 打印请求 url
        log.info("URL            : {}", url);
        // 打印描述信息
        log.info("Description    : {}", myLog.value());
        // 打印 Http method
        log.info("HTTP Method    : {}", methods);
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", className, methodName);
        // 打印请求的 IP
        log.info("IP             : {}", ip);
        // 打印请求入参
        log.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));
        sysLogService.insert(sysLog);
    }
}
